#pragma once

#include "base.h"
#include "../utils/time.h"
#include "../impl/ptable.h"

namespace mdtx::online::collector
{

    template <typename TimeIter, typename ScalaIter>
    class ptable_tumbling : public collector_base<TimeIter, ScalaIter, 3, 3>
    {
        using base = collector_base<TimeIter, ScalaIter, 3, 3>;

    public:
        using typename base::scala_type;
        using typename base::time_type;
        using typename base::tuple_in;
        using typename base::tuple_out;

        using Duration = duration_type_t<time_type>;

        using base::collect;
        using base::update;

    protected:
        time_type next{}, epoch, last{};
        Duration window;

        impl::ptable<scala_type> ptbl{};
        scala_type last_p{}, last_v{}, last_t{};
        bool init{}, colreg{};

    public:
        explicit ptable_tumbling(Duration window, time_type epoch = time_type{})
            : epoch(epoch),
              window(window)
        {
        }

        size_t update(tuple_in const &in) override
        {
            auto [time, price, vol, tnvr] = in;

            if (!init)
            {
                next = align_window_start(time, window, epoch) + window;
                init = true;
            }
            if (colreg)
            {
                // collect required but not called, reset before inserting new values
                ptbl.reset_impl();
                ptbl.insert_impl(last_p, last_v, last_t);
                colreg = false;
            }

            size_t n{};
            if (time > next)
            {
                last = next;
                last_p = price;
                last_v = vol;
                last_t = tnvr;

                while (next < time)
                {
                    next += window;
                }

                colreg = true;
                n = ptbl.table.size();
            }
            else
            {
                ptbl.insert_impl(price, vol, tnvr);
            }

            return n;
        }

        size_t size() const override
        {
            return ptbl.table.size();
        }

        void collect(tuple_out &out) override
        {
            auto [time, price, vol, tnvr] = out;

            auto col_time = colreg ? last : next;
            for (auto [pr, vo, tn] : ptbl)
            {
                *time++ = col_time;
                *price++ = pr;
                *vol++ = vo;
                *tnvr++ = tn;
            }
            ptbl.reset_impl();

            if (colreg)
            {
                // insert temp values
                ptbl.insert_impl(last_p, last_v, last_t);
                colreg = false;
            }
        }
    };

    template <typename TimeIter, typename ScalaIter>
    class ptable_volclk : public collector_base<TimeIter, ScalaIter, 3, 3>
    {
        using base = collector_base<TimeIter, ScalaIter, 3, 3>;

    public:
        using typename base::scala_type;
        using typename base::time_type;
        using typename base::tuple_in;
        using typename base::tuple_out;

        using base::collect;
        using base::update;

    protected:
        time_type last{};
        impl::ptable<scala_type> ptbl{};
        scala_type window, acc{};
        bool init{}, colreg{};

    public:
        explicit ptable_volclk(scala_type window)
            : window(window)
        {
        }

        size_t update(tuple_in const &in) override
        {
            if (colreg)
            {
                // collect required but not called, reset first
                acc = 0;
                ptbl.reset_impl();
            }

            auto [time, price, vol, tnvr] = in;
            ptbl.insert_impl(price, vol, tnvr);
            last = time;
            acc += vol;
            colreg = acc >= window;

            return colreg ? ptbl.table.size() : 0;
        }

        size_t size() const override
        {
            return ptbl.table.size();
        }

        void collect(tuple_out &out) override
        {
            auto [time, price, vol, tnvr] = out;
            for (auto [pr, vo, tn] : ptbl)
            {
                *time++ = last;
                *price++ = pr;
                *vol++ = vo;
                *tnvr++ = tn;
            }
            acc = 0;
            ptbl.reset_impl();
            colreg = false;
        }
    };

    template <typename TimeIter, typename ScalaIter>
    class ptable_tnvrclk : public collector_base<TimeIter, ScalaIter, 3, 3>
    {
        using base = collector_base<TimeIter, ScalaIter, 3, 3>;

    public:
        using typename base::scala_type;
        using typename base::time_type;
        using typename base::tuple_in;
        using typename base::tuple_out;

        using base::collect;
        using base::update;

    protected:
        time_type last{};
        impl::ptable<scala_type> ptbl{};
        scala_type window, acc{};
        bool init{}, colreg{};

    public:
        explicit ptable_tnvrclk(scala_type window)
            : window(window)
        {
        }

        size_t update(tuple_in const &in) override
        {
            if (colreg)
            {
                // flush required but not called, reset first
                acc = 0;
                ptbl.reset_impl();
            }

            auto [time, price, vol, tnvr] = in;
            ptbl.insert_impl(price, vol, tnvr);
            last = time;
            acc += tnvr;
            colreg = acc >= window;

            return colreg ? ptbl.table.size() : 0;
        }

        size_t size() const override
        {
            return ptbl.table.size();
        }

        void collect(tuple_out &out) override
        {
            auto [time, price, vol, tnvr] = out;
            for (auto [pr, vo, tn] : ptbl)
            {
                *time++ = last;
                *price++ = pr;
                *vol++ = vo;
                *tnvr++ = tn;
            }
            acc = 0;
            ptbl.reset_impl();
            colreg = false;
        }
    };
}
